use psd::Psd;
use image::{ImageBuffer, ImageEncoder, Rgba};
use image::codecs::png::PngEncoder;
use image::ExtendedColorType;
use std::ffi::{CStr, CString};
use std::io::Cursor;
use std::os::raw::c_char;
use std::ptr;
use serde_json;

#[repr(C)]
pub struct PsdInfo {
    width: u32,
    height: u32,
    layers: *const c_char,
}

#[no_mangle]
pub extern "C" fn get_psd_info_by_path(file_path: *const c_char) -> *mut PsdInfo {
    let c_str = unsafe {
        assert!(!file_path.is_null());
        CStr::from_ptr(file_path)
    };

    let path = match c_str.to_str() {
        Ok(str) => str,
        Err(_) => return ptr::null_mut(),
    };

    let psd_data = match std::fs::read(path) {
        Ok(data) => data,
        Err(_) => return ptr::null_mut(),
    };

    get_psd_info(&psd_data)
}

#[no_mangle]
pub extern "C" fn get_psd_info_by_data(data_ptr: *const u8, data_len: usize) -> *mut PsdInfo {
    let data = unsafe {
        assert!(!data_ptr.is_null());
        std::slice::from_raw_parts(data_ptr, data_len)
    };

    get_psd_info(data)

}

fn get_psd_info(bytes: &[u8])->*mut PsdInfo{
    let psd = match Psd::from_bytes(bytes) {
        Ok(psd) => psd,
        Err(_) => return ptr::null_mut(),
    };

    let mut psd_info = PsdInfo {
        width: psd.width(),
        height: psd.height(),
        layers: ptr::null(),
    };

    let layers = psd.layers();

    let layers_arr: Vec<(String, i32, i32, i32, i32)> = layers.iter().map(|layer| {
        let name = layer.name().to_string();
        let top = layer.layer_top();
        let left = layer.layer_left();
        let bottom = layer.layer_bottom();
        let right = layer.layer_right();
        (name, top, left, bottom, right)
    }).collect();

    // Convert Vec to JSON string
    let json = serde_json::to_string(&layers_arr).expect("Failed to serialize PSD layers to JSON");

    // Convert JSON string to CString
    let c_string = CString::new(json).expect("CString creation failed");

    // Get C-style string pointer, type *const c_char
    let c_ptr: *const c_char = c_string.into_raw();
    psd_info.layers = c_ptr;

    let psd_info_res = Box::new(psd_info);

    Box::into_raw(psd_info_res)
}

#[no_mangle]
pub extern "C" fn get_psd_image_data_by_path(file_path: *const c_char, out_size: *mut usize) -> *mut u8 {
    let c_str = unsafe {
        assert!(!file_path.is_null());
        CStr::from_ptr(file_path)
    };

    let path = match c_str.to_str() {
        Ok(str) => str,
        Err(_) => return ptr::null_mut(),
    };

    let psd_data = match std::fs::read(path) {
        Ok(data) => data,
        Err(_) => return ptr::null_mut(),
    };

    get_psd_image_data(&psd_data,out_size)
}

#[no_mangle]
pub extern "C" fn get_psd_image_data_by_data(data_ptr: *const u8, data_len: usize,out_size: *mut usize) -> *mut u8 {
    let data = unsafe {
        assert!(!data_ptr.is_null());
        std::slice::from_raw_parts(data_ptr, data_len)
    };

    get_psd_image_data(&data,out_size)
}

fn get_psd_image_data(bytes: &[u8],out_size: *mut usize) -> *mut u8 {
    let psd = match Psd::from_bytes(&bytes) {
        Ok(psd) => psd,
        Err(_) => return ptr::null_mut(),
    };

    let final_image: Vec<u8> = psd.rgba();
    let width = psd.width();
    let height = psd.height();

    let img_buffer: ImageBuffer<Rgba<u8>, Vec<u8>> = match ImageBuffer::from_raw(width, height, final_image) {
        Some(buffer) => buffer,
        None => return ptr::null_mut(),
    };

    let mut png_data = Vec::new();
    {
        let mut encoder = PngEncoder::new(&mut png_data);
        match encoder.write_image(&img_buffer, width, height, ExtendedColorType::Rgba8) {
            Ok(_) => (),
            Err(_) => return ptr::null_mut(),
        };
    }

    unsafe {
        *out_size = png_data.len();
    }

    let png_data = Box::into_raw(png_data.into_boxed_slice()) as *mut u8;
    png_data
}

#[no_mangle]
pub extern "C" fn free_psd_image_data(data: *mut u8, size: usize) {
    if !data.is_null() {
        unsafe {
            let _ = Box::from_raw(std::slice::from_raw_parts_mut(data, size));
        }
    }
}